YAO API DSL 定义
API 定义的目录结构
YAO 所有 API 定义在apis目录下,api 定义文件的后缀名建议使用.http.yao,这是一种与jsonc相同的文件格式,在文档中可以使用注释的json文件格式。
api 层级关系可以可以使用子目录来表示。例如:
apis
├── odata
│ ├── v2
│ │ └── service.http.yao
│ └── v4
│ └── service.http.yao
├── proxy.http.yao1. API 定义的信息
YAO API 采用两级结构定义,以下是一个完整的 API 配置示例:
1.1 第一级结构
在第一级中,定义了 API 的基本信息,包括名称、版本、描述、分组和认证方式等信息。如果未指定group,则使用文件目录作为分组。如果未指定guard,则该 API 为公开 API,无需认证即可调用。
1.2 第二级结构
在第二级中,定义了 API 的具体路径、请求方法、处理函数等信息。paths数组中包含每个 API 的详细配置。第二级中的 guard 优先级高于第一级的 guard。在 process 中可以调用所有 yao 格式的处理器。在 in 中可以指定请求参数,可以是路径参数、查询参数或请求体。在 out 中可以定义响应格式,包括 HTTP 状态码和响应类型等信息。
以下是 API 定义的示例,在创建 api 文件定义时,可以参考以下内容:
{
"name": "chart config", // API名称
"version": "1.0.0", // API版本
"description": "Api for the chart config", // API描述
"group": "", // API分组,默认为文件目录
"guard": "bearer-jwt", // 认证方式
"paths": [
// API路径配置
{
"label": "chart config", // API名称
"description": "chart config", // API描述
"path": "/config", // API请求路径
"method": "POST", // API请求方法
"process": "scripts.amis.chart.getChartUseConfig", // 处理函数
"in": [":payload"], // 请求参数,此处为请求体
"out": {
// 响应格式
"status": 200, // HTTP状态码
"type": "application/json" // 响应类型
}
},
{
"label": "echart data", // API名称
"description": "echart data", // API描述
"path": "/data/:id", // API请求路径,包含路径参数
"method": "GET", // API请求方法
"guard": "scripts.auth.token.CheckToken", // 自定义认证方式
"process": "scripts.amis.chart.getChartById", // 处理函数
"in": ["$param.id", ":query"], // 请求参数,包含路径参数和查询参数
"out": {
// 响应格式
"status": 200, // HTTP状态码
"type": "application/json" // 响应类型
}
}
]
}引用传入的参数
在path中的in传入参数中可以使用以下的引用变量:
| 变量 | 类型 | 说明 |
|---|---|---|
:payload | map[string]any | 请求体(JSON) |
:query | map[string]string[] | 查询参数 |
:form | map[string]string[] | POST 表单 |
:body | string | 原始请求体 |
:fullpath | string | 完整的请求路径 |
:headers | map[string]string[] | 请求头 |
:params | map[string]string[] | 路径参数 |
:query-param | map[string]string[] | 查询参数(同 :query) |
:multi-parts | map[string]string[] | 多部分表单数据 |
:path | string | 请求路径 |
:host | string | 请求的主机名 |
:schema | string | 请求的协议(如 http 或 https) |
:remote-host | string | 远程主机地址 |
:ip | string | 客户端 IP 地址 |
$form.* | any | 表单字段值 |
$param.* | any | 路径参数值 |
$query.* | any | 查询参数值 |
$payload.* | any | 请求体中的字段值 |
$session.* | any | 会话数据 |
$header.* | any | 请求头中的字段值 |
$file.* | File | 上传的文件对象 |
1.1 路径参数
使用$param.参数名获取路径中的参数,例如:
{
"path": "/data/:id",
"in": ["$param.id"] // 获取路径中的id参数
}1.2 查询参数
使用:query获取 URL 查询参数:
{
"in": [":query"] // 获取所有查询参数
}1.3 请求体
使用:payload获取请求体内容:
{
"in": [":payload"] // 获取POST请求体
}1.4 自定义 Guard
可以在路径级别指定自定义 Guard:
{
"guard": "scripts.auth.token.CheckToken" // 使用自定义的token检查
}可自定义guard,guard处理器的参数有:
- 1
api路径 - 2
api参数对象 - 3
query参数对象 - 4
body请求payload,如果是 application/json,是对象,其它是字符串 - 5
header请求抬头
示例:在js函数里定义一个guard,自定义的 guard 处理器接收 5 个参数,如果发现数据异常,直接返回 Exception.
/**
* Custom guard
* @param {*} api path 完整的请求地址
* @param {*} api params 所有的参数化的值
* @param {*} query string 请求?后的对象值
* @param {*} payload 请求正文
* @param {*} Request headers http头部信息
*/
function Guard(path, params, query, payload, headers) {
isTest = headers['Unit-Test'] ? headers['Unit-Test'] : [];
if (isTest[0] == 'yes') {
throw new Exception('Unit-test throw', 418);
}
// query对象格式
// "query:"
// {
// "key": [
// "xxx"
// ],
// "name": [
// "xx"
// ]
// }
}1.5 输出格式
自定义输出格式:
{
"out": {
"status": 200,
"type": "text/html", //设置输出类型格式
"body": "<h1>Hello, World!</h1>",
"headers": {}
}
}更灵活的输出格式,在 header 与 body 中可以使用绑定处理器返回的内容。Content-Type是一个非常重要的参数,一定要设置。
{
"out": {
"status": 200,
"body": "{{content}}", //定义返回的body内容
"headers": { "Content-Type": "{{type}}" } //自定义返回的headers内容
}
}跳转
如果需要跳转到其它地址,可以使用redirect字段。由于定义了redirect,此路由会在调用处理器后,直接跳转到其它地址,而不会返回响应。
{
"out": {
"redirect": {
"code": 301, //默认是301
"location": "" //跳转新地址
}
}
}请求方法 (HTTP Methods)
YAO API 支持以下 HTTP 请求方法:
- GET: 用于获取资源。GET 请求应该只用于读取数据,而不应该产生副作用。
- POST: 用于创建新资源或提交数据。POST 请求通常用于表单提交或上传文件。
- PUT: 用于更新现有资源或替换资源。PUT 请求通常用于更新整个资源。
- DELETE: 用于删除资源。DELETE 请求用于删除指定的资源。
- PATCH: 用于部分更新资源。PATCH 请求通常用于更新资源的某些字段,而不是整个资源。
- HEAD: 类似于 GET 请求,但只返回响应头,不返回响应体。HEAD 请求通常用于检查资源是否存在或获取资源的元数据。
- OPTIONS: 用于获取服务器支持的 HTTP 方法。OPTIONS 请求通常用于跨域请求的预检请求。
- ANY: 用于匹配任何 HTTP 方法。ANY 请求可以处理 GET、POST、PUT、DELETE 等所有 HTTP 方法。
示例
以下是一些常见的 HTTP 方法使用示例:
GET 请求
{
"path": "/data/:id",
"method": "GET",
"process": "scripts.data.getDataById",
"in": ["$param.id"],
"out": {
"status": 200,
"type": "application/json"
}
}2. API Guard
API Guard 用于请求拦截,支持身份验证、权限检查等功能。
- 支持多级继承,Path.Guard > HTTP.Guard > 系统默认("bearer-jwt")
- 特殊值"-"表示公开 API
- 支持多个 Guard 组合:"bearer-jwt,scripts.pet.Guard"
内置 Guard 列表:
var Guards = map[string]gin.HandlerFunc{
"bearer-jwt": guardBearerJWT, // Bearer JWT
"query-jwt": guardQueryJWT, // 从查询参数获取JWT
"cross-origin": guardCrossOrigin, // 跨域处理
"table-guard": table_v0.Guard, // 表格Guard
"widget-table": table.Guard, // 表格组件Guard
"widget-list": list.Guard, // 列表组件Guard
"widget-form": form.Guard, // 表单组件Guard
"widget-chart": chart.Guard, // 图表组件Guard
"widget-dashboard": dashboard.Guard, // 仪表盘组件Guard
}3. 内置服务 API
在 Yao 中,提供了一些可以直接调用的内置 API 接口。这些接口的调用方式与普通 API 相同,但它们的参数和路径已经被固定,无需手动指定。
3.1 内置服务
- POST
/api/__yao/app/service/:name: 调用 services 目录下的 JS 文件。 - GET
/api/__yao/app/setting: 获取当前应用的配置信息。 - POST
/api/__yao/app/setting: 更新当前应用的配置信息。 - GET
/api/__yao/app/menu: 获取当前应用的菜单信息。 - GET
/api/__yao/app/icons/:name: 获取图标信息。 - POST
/api/__yao/app/setup: 应用初始化。 - POST
/api/__yao/app/check: 应用检查。
自动加载的 API
如果用户定义了特定的资源(如 tables, forms, lists, charts),系统会自动加载相应的 API。
neo
api 前缀:/api/__yao/neo
sui
api 前缀:/api/__yao/sui
widget
api 前缀:/api/widget/<ID>
登录
- 管理员登录 api 前缀:
/api/__yao/login/admin - 用户登录 api 前缀:
/api/__yao/login/user
Dashboard 定义
当用户定义了 dashboard 时,定义文件需要保存在目录dashboard目录下,每一个dashboard对应的 API 将会被加载:
/api/__yao/dashboard/:id/data: 获取仪表盘数据。/api/__yao/dashboard/:id/setting: 获取仪表盘设置。/api/__yao/dashboard/:id/component/:xpath/:method: xpath 为组件的 xpath,method 为组件的方法。
Tables 定义
当用户定义了 tables 时,定义文件需要保存在目录tables目录下,每一个table对应的 API 将会被加载:
/api/__yao/table/:id/search: 搜索数据。/api/__yao/table/:id/get: 获取数据。/api/__yao/table/:id/find/:primary: 根据主键查找数据。/api/__yao/table/:id/save: 保存数据。/api/__yao/table/:id/create: 创建数据。/api/__yao/table/:id/insert: 插入数据。/api/__yao/table/:id/update/:primary: 根据主键更新数据。/api/__yao/table/:id/update/in: 批量更新数据。/api/__yao/table/:id/update/where: 根据条件更新数据。/api/__yao/table/:id/delete/:primary: 根据主键删除数据。/api/__yao/table/:id/delete/in: 批量删除数据。/api/__yao/table/:id/delete/where: 根据条件删除数据。/api/__yao/table/:id/upload/:xpath/:method: 上传文件。/api/__yao/table/:id/download/:field: 下载文件。
Forms 定义
当用户定义了 forms 时,定义文件需要保存在目录forms目录下,每一个form对应的 API 将会被加载:
/api/__yao/form/:id/find/:primary: 根据主键查找数据。/api/__yao/form/:id/save: 保存数据。/api/__yao/form/:id/create: 创建数据。/api/__yao/form/:id/update/:primary: 根据主键更新数据。/api/__yao/form/:id/delete/:primary: 根据主键删除数据。/api/__yao/form/:id/upload/:xpath/:method: 上传文件。/api/__yao/form/:id/download/:field: 下载文件。
Lists 定义
当用户定义了 lists 时,定义文件需要保存在目录lists目录下,每一个list对应的 API 将会被加载:
/api/__yao/list/:id/get: 获取数据。/api/__yao/list/:id/save: 保存数据。/api/__yao/list/:id/upload/:xpath/:method: 上传文件。/api/__yao/list/:id/download/:field: 下载文件。
Charts 定义
当用户定义了 charts 时,以下 API 将会被加载:
/api/__yao/chart/:id/data: 获取图表数据。
Service API
访问前缀: /api/__yao/app/service/:name
在 Yao 中,你可以通过定义服务(Service)来创建 API。服务文件需要保存在services/目录下,并以.js或是.ts形式存在。
示例:获取表结构信息
在 services 目录下创建 js 文件定 义服务:
// schema.js
function getTables() {
const list = Process('schemas.default.Tables');
return { rows: list.map((table) => ({ item: table })) };
}- 调用方式:
- 请求方法:post
- 请求地址:
http://127.0.0.1:5099/api/__yao/app/service/schema - 请求参数:
{
"args": [], // 参数列表
"method": "getTables" // 调用的方法名,即是在js文件中定义的函数名
}curl -X POST http://127.0.0.1:5099/api/__yao/app/service/schema \
-H 'Content-Type: application/json' \
-d '{ "args":[],"method":"getTables"}'4. Stream API
此类型的 API 支持流式数据的传输,比如各种语言模型的流式调用。
支持流式 API,在 out.type 中指定text/event-stream:
{
"path": "/ask-stream",
"method": "POST",
"process": "scripts.ai.stream.Call",
"out": {
"type": "text/event-stream; charset=utf-8"
}
}服务端使用ssEvent发送数据:
function collect(content) {
ssEvent('message', content);
}示例
可以在 Yao 中使用 处理器http.Stream调用其它 AI 服务,然后再向客户端输出内容,相当于一个 ai 代理服务器,需要注意的是向 AI 服务端发出请求时需要进行异步响应处理。与其它 http 处理器不一样的地方在于第三个参数需要是一个回调函数(js 函数)。
http.Stream('POST', url, handler, RequestBody, null, {
Accept: 'text/event-stream; charset=utf-8',
'Content-Type': 'application/json',
Authorization: `Bearer ` + setting.api_token
});比如这里是调用 openai 的 api,然后通过回调函数将数据流式传输给客户端。另外需要注意处理函数要与上面的请求函数在同一个 js 文件里。
function handler(payload) {
const lines = payload.split('\n\n');
for (const line of lines) {
if (line === '') {
continue;
}
if (line === 'data: [DONE]') {
return 0;
} else if (line.startsWith('data:')) {
const myString = line.substring(5);
try {
let message = JSON.parse(myString);
if (message) {
reply = message;
let content = message.choices[0]?.delta?.content;
// console.log(`content:${content}`);
if (content) {
g_message += content;
ssEvent('message', content);
}
}
} catch (error) {
ssEvent('errors', error.Error());
return -1;
}
} else {
console.log('unexpected', line);
}
}
//异常,返回-1
//正常返回1,默认
//中断返回0
return 1;
}5. 文件上传下载
文件上传:
{
"path": "/upload",
"method": "POST",
"process": "fs.system.Upload",
"in": ["$file.file"],
"out": {
"status": 200,
"type": "application/json"
}
}文件下载:
{
"path": "/download",
"method": "GET",
"process": "fs.system.Download", //内置的下载处理器
"in": ["$query.name"],
"out": {
"status": 200,
"body": "{{content}}",
"headers": { "Content-Type": "{{type}}" }
}
}